﻿using System;
using System.Collections.Generic;
using System.Linq;
using Garfield.Core.Runtime;
using Garfield.Core.Runtime.SPI;

namespace Garfield.Core
{
    public sealed class Platform
    {
        public const string SERVICES = "Garfield.Core.Services";
        public static readonly Plugin.ExtensionPoint<Api, Implementation> Services;

        [ThreadStatic]
        private static Platform platform;

        static Platform()
        {
            platform = null;
            Services = new Plugin.ExtensionPoint<Api, Implementation> { Identifier = SERVICES, IsCachingExtension = true, PluginIdentifier = null };
        }

        public static Platform Current
        {
            get
            {
                if (platform == null)
                    throw new PlatformException("There is no platform instance attached to current thread");
                return platform;
            }
        }

        public static void Initialize(IPlatformProvider platformProvider)
        {
            if (platform != null)
                throw new PlatformException("There is already a platform instance attached to current thread");
            platform = new Platform(platformProvider);
        }

        private readonly IPlatformProvider provider;
        private readonly Dictionary<string, Plugin> installedPlugins = new Dictionary<string, Plugin>();
        private readonly List<string> started = new List<string>();

        private Platform(IPlatformProvider provider)
        {
            this.provider = provider;
            Install(new [] {new CorePlugin()});
        }

        public void Install(IEnumerable<Plugin> plugins)
        {
            foreach (var plugin in plugins)
            {
                installedPlugins.Add(plugin.Identifier, plugin);                
            }
        }

        private IEnumerable<Plugin> ResolveDependencies(IEnumerable<Plugin> plugins)
        {
            var dependencies = new List<string>();
            foreach (var plugin in plugins)
            {
                ResolveDependencies(plugin, new List<string>(), dependencies);
            }            
            return dependencies.Select(id => installedPlugins[id]);
        }

        private void ResolveDependencies(Plugin plugin, ICollection<string> visiting, List<string> dependencies)
        {
            if (visiting.Contains(plugin.Identifier))
            {
                throw new PlatformException("cyclic dependency found : " + plugin.Identifier);
            }
            if (dependencies.Contains(plugin.Identifier)) return;
            visiting.Add(plugin.Identifier);
            foreach (var dependencyIdentifier in plugin.Dependencies)
            {                
                if (!installedPlugins.ContainsKey(dependencyIdentifier))
                {
                    throw new PlatformException("required plugin " + dependencyIdentifier + " not found");
                }
                ResolveDependencies(installedPlugins[dependencyIdentifier], visiting, dependencies);
            }
            dependencies.AddRange(plugin.Dependencies.Where(depedency => !dependencies.Contains(depedency)));
            dependencies.Add(plugin.Identifier);
            visiting.Remove(plugin.Identifier);
        }


        private void RegisterExtensions(Plugin plugin)
        {
            foreach (var extension in plugin.Extensions)
            {
                RegisterExtension(extension);
            }
        }

        private void RegisterExtension(Plugin.Extension extension)
        {
            if (extension.Implementation != null)
            {
                provider.RegisterExtension(extension.ExtensionPoint.Identifier, extension.Identifier, extension.Implementation, extension.Initializer);
            } else
            {
                provider.RegisterExtension(extension.ExtensionPoint.Identifier, extension.Identifier, extension.Instance);
            }
        }

        private void RegisterExtensionPoints(Plugin plugin)
        {
           foreach (var extensionPoint in plugin.ExtensionPoints)
            {
                provider.RegisterExtensionPoint(extensionPoint);
            }
        }

        public void Start(params string[] pluginIdentifiers)
        {
            var dependencies = ResolveDependencies(pluginIdentifiers.Select(id => installedPlugins[id])).ToList();

            var serivceLocator = (IServiceLocator)provider.GetService(typeof(IServiceLocator));

            foreach (var plugin in dependencies)
            {
                if (!started.Contains(plugin.Identifier))
                    RegisterExtensionPoints(plugin);
            }
            foreach (var plugin in dependencies)
            {
                if (started.Contains(plugin.Identifier)) continue;
                RegisterExtensions(plugin);
                plugin.Start(serivceLocator);
                started.Add(plugin.Identifier);
            }
        }

        public void Shutdown()
        {
            try
            {
                var serivceLocator = (IServiceLocator)provider.GetService(typeof(IServiceLocator));
                var dependencies = ResolveDependencies(started.Select(id => installedPlugins[id]));
                foreach (var dependency in dependencies.Reverse())
                {
                    dependency.Stop(serivceLocator);
                }
            }
            finally
            {
                platform = null;
            }
        }
    }

    public class PlatformException : Exception
    {
        public PlatformException(string message)
            : base(message)
        {
        }

        public PlatformException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }
}
